package com.xuzmian.demo.kotlin

import kotlin.properties.Delegates
import kotlin.reflect.KProperty

/**
 * Kotlin 支持 委托属性:
 * 语法是： val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该 委托， 因为属性对应的 get()
 * （与 set()）会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口，但是需要提
 * 供一个 getValue() 函数（与 setValue()——对于 var 属性）。
 *
 * 对于一个只读属性（即 val 声明的），委托必须提供一个名为 getValue 的函数，该函数接受以下参数：
 * 1. thisRef —— 必须与 属性所有者 类型（对于扩展属性——指被扩展的类型）相同或者是它的超类型；
 * 2. property —— 必须是类型 KProperty<*> 或其超类型。
 * 3. 这个函数必须返回与属性相同的类型（或其子类型）。
 * 4. 函数需要用 operator 关键字来进行标记。
 *
 * 对于一个可变属性（即 var 声明的），委托必须额外提供一个名为 setValue 的函数，该函数接受以下参数：
 * 1. thisRef —— 同 getValue()；
 * 2. property —— 同 getValue()；
 * 3. new value —— 必须与属性同类型或者是它的子类型。
 * 4. 函数需要用 operator 关键字来进行标记。
 */
class Example {
    var p: String by Delegate()
}

/**
 * 委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口之一。 这俩接口是在 Kotlin 标准库中声明的:
 *
 * interface ReadOnlyProperty<in R, out T> {
 * operator fun getValue(thisRef: R, property: KProperty<*>): T
 * }
 *
 * interface ReadWriteProperty<in R, T> {
 * operator fun getValue(thisRef: R, property: KProperty<*>): T
 * operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
 * }
 */
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

fun  DelegatedPropertiesTest(){
    val e = Example()
    println(e.p)  //从委托到一个 Delegate 实例的 p 读取时，将调用 Delegate 中的 getValue() 函数
    e.p = "NEW" //e.p = "NEW"
}


/**
 * 延迟属性 Lazy:
 * lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数，返回的实例可以作为实现延迟属性的委托：
 * 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果， 后续调用 get() 只是返回记录的结果。
 */
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

/**
 * 默认情况下，对于 lazy 属性的求值是同步锁的（synchronized）：该值只在一个线程中计算，并且所有线程会看到相同的值。
 * 如果初始化委托的同步锁不是必需的，这样多个线程可以同时执行，那么将 LazyThreadSafetyMode.PUBLICATION 作为参数
 * 传递给 lazy() 函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程， 那么可以使用 LazyThreadSafetyMode.NONE
 * 模式：它不会有任何线程安全的保证以及相关的开销
 */
fun lazyValueTest() {
    println(lazyValue)
    println(lazyValue)
}


/**
 * 可观察属性 Observable:
 * Delegates.observable() 接受两个参数：初始值与修改时处理程序（handler）。 每当我们给属性赋值时会调用该处理程序（在赋值后执行）。
 * 它有三个参数：被赋值的属性、旧值与新值.
 *
 * 如果你想截获赋值并“否决”它们，那么使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。
 */
class ObserableProperty {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun ObserablePropertyTest() {
    val obserableProperty = ObserableProperty()
    obserableProperty.name = "first"
    obserableProperty.name = "second"
}

/**
 * 把属性储存在映射中:
 */
class PropertiesMap(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}


fun PropertiesMapTest(){
    val user = PropertiesMap(mapOf(
            "name" to "John Doe",
            "age"  to 25
    ))
    println(user.name) // Prints "John Doe"
    println(user.age)  // Prints 25
}

/**
 * 局部委托属性:局部委托属性.
 * memoizedFoo 变量只会在第一次访问时计算。 如果 computeFoo == null，那么该变量根本不会计算。
 */
fun localDelegatedPropertyTest(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (null != computeFoo) {
        memoizedFoo.hashCode()
    }
}

/**
 * Not Null:适用于那些无法在初始化阶段就确定属性值的场合。
 *
 * 注意：以下代码：如果属性在赋值前就被访问的话则会抛出异常。
 * notNullDelegated.notNullBar = "bar"
 * println(notNullDelegated.notNullBar)
 */
class NotNullDelegated {
    var notNullBar: String by Delegates.notNull<String>()
}

